package impls.agents;

import impls.classes.Constants;
import impls.classes.Position;
import interfaces.agents.Car;
import jade.core.AID;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.AMSService;
import jade.domain.FIPAAgentManagement.AMSAgentDescription;
import jade.domain.FIPAAgentManagement.SearchConstraints;
import jade.lang.acl.ACLMessage;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

/**
 * agent representing a car
 * 
 * @author thailow
 * 
 */
public class CarAgent extends ActorAgent implements Car {

	private static final long serialVersionUID = -7635203115665931003L;

	private static final String TYPE = Constants.CAR;

	private int fuel = 80;

	private AID gasStation;

	private boolean choosingStation = false;

	private String lastFuelCompany = "none";

	private Hashtable<String, Long> lastRefuels = new Hashtable<String, Long>();

	private Hashtable<AID, String[]> gasStations = new Hashtable<AID, String[]>();

	private boolean drivesToStation = false;

	private ArrayList<Integer> route;

	private int received = 0;

	/**
	 * setup method of CarAgent
	 */
	public void setup() {
		// general setup
		super.setup();
		super.TYPE = TYPE;

		Random r = new Random();

		// reading command line arguments
		Object[] args = getArguments();

		// starting car at random position and chosen/random fuel level
		if (args != null && args.length > 0) {
			// create with chosen fuel level
			Integer i = Integer.valueOf(args[0].toString());
			fuel = i.intValue();
		}

		else {
			// create with random fuel level
			sendMessage("Creating " + TYPE + " with random fuel. ");
			while (fuel < Constants.MINFUEL) {
				fuel = r.nextInt(Constants.MAXFUEL + 1);
			}
		}

		sendMessage(toString() + "Engine started at position (" + pos.getX()
				+ "," + pos.getY() + "). Fuel level: " + fuel);

		// create initial random route
		createRandomRoute();

		// add behaviour for motion and graphics update
		addBehaviour(new TickerBehaviour(this, Constants.INTERVAL) {
			private static final long serialVersionUID = -1130571732619089284L;

			// drive to next position and send position to graphics agent
			protected void onTick() {
				drive();
				String[] include = { Integer.toString(fuel) };
				sendPos(include);
			}
		});

		// add behaviour for message handling
		addBehaviour(new CyclicBehaviour(this) {
			private static final long serialVersionUID = 333055672669947086L;

			Timestamp lastReceived = new Timestamp(System.currentTimeMillis());

			public void action() {
				ACLMessage msg = receive();
				if (msg != null) {
					// receiving messages from gas stations
					if (msg.getConversationId().equals(Constants.MYFUELPRICE)) {
						lastReceived = new Timestamp(System.currentTimeMillis());
						received++;
						// processing Message String
						String[] temp = msg.getContent().split(",");
						String[] values = new String[temp.length];
						for (int i = 0; i < temp.length; i++) {
							values[i] = temp[i];
						}
						if ((msg.getSender() != null) && (values != null)) {
							// adding senders to an array
							gasStations.put(msg.getSender(), values);
						}
					}
					// receiving initial message from graphics agent
					else if (msg.getConversationId().equals(
							Constants.GRAPHICSAID)) {
						graphicsAID = new AID(msg.getContent(), false);
					}
					// update station route if necessary
				} else if (drivesToStation && choosingStation) {
					// makes agent wait for other messages
					Timestamp ts = new Timestamp(System.currentTimeMillis()
							- Constants.RECEIVEINTERVAL);
					if (lastReceived.before(ts)) {
						cheapestStation();
						received = 0;
					}
				}
			}
		});
	}

	/**
	 * method for motion of agent
	 * 
	 */
	public void drive() {
		// general driving
		if (!route.isEmpty()) {
			fuel--;
			int dir = route.remove(0);
			pos.move(dir);
			// testing if gas station was reached and refueling if so
			if (gasStation != null && drivesToStation) {
				String[] t = gasStations.get(gasStation);
				// testing if station was really reached
				if ((pos.getX() == Float.valueOf(t[1]).intValue())
						&& (pos.getY() == Float.valueOf(t[2]).intValue())) {
					// System.out.println(toString() + ": Refueled!");
					sendRefueled();
					fuel = Constants.MAXFUEL;
				}
			}
			// calculation of new routes
		} else {
			// mode drivesToStation
			if (drivesToStation) {
				if (!choosingStation) {
					createRandomRoute();
					// System.out.println(getAID().getName()
					// + ": Cruising randomly.");
					drivesToStation = false;
				}
				// mode cruising randomly
			} else if (!drivesToStation) {
				choosingStation = true;
				reqStationInfo();
				// System.out.println(getAID().getName()
				// + ": Fuel low. Driving to station.");
				drivesToStation = true;
			}
		}
	}

	/**
	 * calculating random route until MINFUEL is reached
	 * 
	 */
	public void createRandomRoute() {
		route = new ArrayList<Integer>();
		Random r = new Random();
		int dir = r.nextInt(4);
		for (int steps = fuel - Constants.MINFUEL; steps > 0; steps--) {
			if (r.nextInt(4) == 0) {
				int newDir = r.nextInt(4);
				while (dir + newDir == 2 || dir + newDir == 4) {
					newDir = r.nextInt(4);
				}
				route.add(dir = newDir);
			} else {
				route.add(dir);
			}
		}
	}

	/**
	 * asks fuel stations for position and fuel price
	 * 
	 */
	public void reqStationInfo() {
		// creating a list of all agents
		AMSAgentDescription[] agents = null;
		try {
			SearchConstraints c = new SearchConstraints();
			c.setMaxResults(new Long(-1));
			agents = AMSService.search(this, new AMSAgentDescription(), c);
		} catch (Exception e) {
			System.out.println(toString() + "Problem searching AMS: " + e);
			e.printStackTrace();
		}

		// create message including lastFuelCompany and info from lastRefuels
		ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
		msg.setConversationId(Constants.FUELPRICE);
		StringBuffer lastRefuelsSB = new StringBuffer(lastFuelCompany + ",");
		for (Enumeration<String> e = lastRefuels.keys(); e.hasMoreElements();) {
			String current = e.nextElement();
			lastRefuelsSB
					.append(current + ":" + lastRefuels.get(current) + ";");
		}
		if (lastRefuelsSB.toString().endsWith(",")) {
			lastRefuelsSB.append("none:0");
		}
		msg.setContent(lastRefuelsSB.toString());

		// sending to all agents
		for (int i = 0; i < agents.length; i++) {
			if (agents[i].getName() != getAID()) {
				msg.addReceiver(agents[i].getName());
			}
		}
		send(msg);
	}

	/**
	 * finds the cheapest station and calls planRoute()
	 * 
	 */
	public void cheapestStation() {
		float minCost = 100000, cost = -1;

		Position bestPos = new Position(99999999, 999999999);
		Position stationPos;

		String values[];
		Integer distX = 0, distY = 0, dist = 0;
		AID current;

		// checking for cheapest gas station
		for (Enumeration<AID> e = gasStations.keys(); e.hasMoreElements();) {
			current = (AID) e.nextElement();
			values = gasStations.get(current);

			stationPos = new Position(Integer.valueOf(values[1]), Integer
					.valueOf(values[2]));

			dist = pos.minDist(stationPos);
			cost = dist * Float.valueOf(values[3]);

			// save current fuel station as cheapest, if costs are lower
			if ((cost < minCost) && (dist <= fuel)) {
				bestPos = stationPos;
				gasStation = current;
				lastFuelCompany = values[0].toString();
				minCost = cost;
			}
		}

		sendMessage(toString() + "Moving to Station (" + bestPos.getX() + ","
				+ bestPos.getY() + ").");

		route = new ArrayList<Integer>();

		dist = pos.minDist(bestPos);
		distX = pos.dist(pos.getX(), bestPos.getX());
		distY = pos.dist(pos.getY(), bestPos.getY());

		planRoute(Constants.RIGHT, distX);
		planRoute(Constants.UP, distY);

		// saving AID of cheapest fuel station
		choosingStation = false;

		// car dies if fuel is to low and all tations are to far away
		if (dist > fuel) {
			sendMessage(toString() + "Cannot reach next gas station with "
					+ fuel + " fuel (" + dist + " needed, " + distX
					+ " in x and " + distY + " in y). Switching off engine.");
			doDelete();
		}
	}

	/**
	 * plans route with given distance (can be in x- or y-direction) and base
	 * direction (UP or RIGHT)
	 * 
	 * @param baseDir
	 * @param dist
	 */
	public void planRoute(int baseDir, int dist) {
		for (int i = 0; i < Math.abs(dist); i++) {
			if (dist > 0) {
				route.add(baseDir);
			}
			if (dist < 0) {
				route.add(baseDir + 2);
			}
		}
	}

	/**
	 * informs fuel station about refuel
	 */
	public void sendRefueled() {
		lastRefuels.put(lastFuelCompany, System.currentTimeMillis());
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
		msg.setConversationId(Constants.REFUELED);
		msg.setContent((new Integer(Constants.MAXFUEL - fuel)).toString());
		msg.addReceiver(gasStation);
		send(msg);
	}
}
